#include "TwoWayList.h"
#include "Record.h"
#include "Schema.h"
#include "File.h"
#include "Comparison.h"
#include "ComparisonEngine.h"
#include "Defs.h"
#include "DBFile.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>

//--------------------------FileMetaData---------------------------------
FileMetadata::FileMetadata() {

}

//File name is generated in same directory as a hidden file with .meta extension.
//Example: DB File: cutsomer.bin, Metadata file: .customer.bin.meta
void FileMetadata::GenerateMetadataName(char* f_path, char** f_metadata) {
	*f_metadata = new char[strlen(f_path) + sizeof(".meta")];
	memcpy(*f_metadata, f_path, strlen(f_path));
	memcpy(*f_metadata + strlen(f_path), ".meta", sizeof(".meta"));
	//cout << "Meta file name is: " << *f_metadata << "\n";
	return;
}

//The structure FileMetadata.myData is written into the file. Uses
//FileMetaDataLLGenerateMetaDataName to generate file name.
int FileMetadata::WriteMetaData(char* f_path) {
	char* f_metadata;
	GenerateMetadataName(f_path, &f_metadata);
	int flags = O_TRUNC | O_RDWR | O_CREAT;
	int mode = S_IRUSR | S_IWUSR;
	int fileCreate = open(f_metadata, flags, mode);
	if (fileCreate < 0) {
		cerr << "MBAD!  Open did not work for " << f_metadata << "\n";
		exit(1);
	}
	delete f_metadata;
	int ret = write(fileCreate, reinterpret_cast<char*>(&mydata),
			sizeof(mydata));
	close(fileCreate);
	return ret;
}

//Loads data into structure FileMetaData.myData
int FileMetadata::LoadMetaData(char* f_path) {
	char* f_metadata;
	GenerateMetadataName(f_path, &f_metadata);

	int flags = O_RDONLY;
	int mode = S_IRUSR;
	int fileCreate = open(f_metadata, flags, mode);
	if (fileCreate < 0) {
		cerr << "LMBAD!  Open did not work for " << f_metadata << "\n";
		exit(1);
	}
	delete f_metadata;
	int ret = read(fileCreate, reinterpret_cast<char*>(&mydata),
			sizeof(mydata));

	close(fileCreate);
	return ret;
}

//--------------------------Heap---------------------------------
int Heap::writeDirtyPage() {
	if (isDirty) {
		if (!myFile->AddPage(myPage, currentPage)) {
			cerr << "Can not write dirty page!!\n";
			exit(1);
		}
		isDirty = false;
		isAddDone = false;
	}
	return 1;
}

//Creates an object of File, Page and Record class.
Heap::Heap() {
	Initialize();
}

// Creates the file and keeps it OPEN.
// It is not closed based on usage scenario in test1() of file test.cc which does Heap::Load()
// immediately after Create()
//TODO: Analyze case where Create/Open is called in between handling a file 
//i.e., a sequence like Open, MoveFirst, GetNextRecord, Add, Open
Heap::Heap(char* f_path, int open, FileMetadata *meta) {
	Initialize();
	myFile->Open(open, f_path);
	myMetadata = meta;
}

void Heap::Initialize() {
	myFile = new File();
	myPage = new Page();
	currentRecord = NULL;
	currentPage = -1;
	lastReadPage = -1;
	lastReadRecord = 0;
	isDirty = false;
	isAddDone = false;

}

Heap::~Heap() {
	delete myFile;
	delete myPage;
}

//Text file -> binary file
void Heap::Load(Schema &f_schema, char *loadpath) {
	if (loadpath == NULL) {
		cerr << "BAD! Specified load path is incorrect\n";
		exit(1);
	}

	Record record;
	FILE *tableFile = fopen(loadpath, "r");
	if (tableFile == NULL) {
		cerr << "BAD! File could not be opened: " << loadpath << "\n";
		exit(1);
	}

	while (record.SuckNextRecord(&f_schema, tableFile)) {
		this->Add(record);
	}
	//myFile->AddPage(myPage,currentPage);
	fclose(tableFile);
}

void Heap::MoveFirst() {
	//Check if current page is dirty and write into file
	if (currentPage != 0) {
		writeDirtyPage();
		currentPage = 0;
		lastReadPage = 0;
		lastReadRecord = 0;
		isAddDone = false;
		myFile->GetPage(myPage, currentPage);
		myPage->GetFirst(currentRecord, false);
	}
}

//myFile->Close returns the number of pages in it and can be zero. So DONT use it as return value.
int Heap::Close() {
	//Check if current page is dirty and write into file
	writeDirtyPage();
	myFile->Close();
	return 1;
}

//record is deleted by myPage->Append
void Heap::Add(Record &record) {
	//NOTE: this variable is lastPage. not to be confused with lastReadPage
	off_t lastPage = myFile->GetLength() - 2;
	if ((lastPage >= 0) && (lastPage > currentPage)) {
		myFile->GetPage(myPage, lastPage);
		currentPage = lastPage;
	} else if (lastPage == -2) {
		currentPage = 0;
	}

	if (!myPage->Append(&record)) {
		writeDirtyPage();
		currentPage++;
		myPage->EmptyItOut();
		myPage->Append(&record);
	}
	isDirty = true;
	isAddDone = true;
}

int Heap::GetNext(Record &fetchme) {
	//cout << "My last page:" << lastReadPage << " Current page:"<< currentPage << " LastReadRecord:" <<lastReadRecord << "\n";
	if ((lastReadPage > 0) && (lastReadPage != currentPage)) {
		writeDirtyPage();
		myFile->GetPage(myPage, lastReadPage);
		currentPage = lastReadPage;
		myPage->GoToRecord(lastReadRecord);
	} else {
		if (isAddDone) {
			myPage->GoToRecord(lastReadRecord);
			isAddDone = false;
		}
	}

	if (!myPage->GetNextRecord(fetchme)) {
		writeDirtyPage();
		if ((currentPage + 1) < (myFile->GetLength() - 1)) {
			currentPage++;
			myFile->GetPage(myPage, currentPage);
			lastReadPage = currentPage;
			lastReadRecord = 0;
			myPage->GetFirst(currentRecord, false);
			myPage->GetNextRecord(fetchme);

		} else {
			return 0;
		}
	}
	lastReadRecord++;
	return 1;
}

int Heap::GetNext(Record &fetchme, CNF &cnf, Record &literal) {
	ComparisonEngine comp;
	do {
		if (this->GetNext(fetchme) == 0) {
			return 0;
		}
	} while (!comp.Compare(&fetchme, &literal, &cnf));

	return 1;
}

//--------------------------Sorted---------------------------------
Sorted::Sorted() {
	Initialize();
}

Sorted::Sorted(char *fpath, int open, FileMetadata *meta) {
	Initialize();
	filePath = fpath;
	myFile->Open(open, fpath);
	myMetadata = meta;
}

void Sorted::Initialize() {
	myFile = new File();
	myPage = new Page();
	myMode = reading;
	currentRecord = NULL;
	currentPage = -1;
	inPipe = NULL;
	outPipe = NULL;
	queryOrderMakerSet = false;
	matchingPage = false;
	oldPage = NULL;
	oldPageNum = -1;
	pageAdded = false;
	matchingPage = false;
	set = 0;

}

void Sorted::setMode(mode m) {
	if (myMode == m)
		return;
	myMode = m;
	if (myMode == writing) {
		/*
		 //Reset parameters of reading
		 currentPage = -1;
		 currentRecord = NULL; */
		//Create the input,output Pipe,BigQ	
		inPipe = new Pipe(100);
		outPipe = new Pipe(100);
		myQueue = new BigQ(*inPipe, *outPipe, myMetadata->mydata.om,
				myMetadata->mydata.runLength);
	} else if (myMode == reading) {
		writeIntoFile();
		delete inPipe;
		delete outPipe;
		delete myQueue;
	}
}

void Sorted::writeIntoFile() {
	inPipe->ShutDown();
	queryOrderMakerSet = false;
	File *tmpFile = new File();
	tmpFile->Open(0, "bin.tmp");
	Page *tmpPage = new Page();
	off_t tmpPgNum = -1;
	int recReadFromFile = 0;
	int done = 0;
	Record *recFromPipe = new Record();
	Record *recFromFile = new Record();
	this->MoveFirst();
	if (this->GetNext(*recFromFile))
		recReadFromFile = 1;

	while (outPipe->Remove(recFromPipe)) {

		while (recReadFromFile
				&& (ceng.Compare(recFromPipe, recFromFile,
						&myMetadata->mydata.om) != -1)) {
			AddToPage(recFromFile, tmpPage, tmpFile, &tmpPgNum);
			if (this->GetNext(*recFromFile))
				recReadFromFile = 1;
			else
				recReadFromFile = 0;
		}

		AddToPage(recFromPipe, tmpPage, tmpFile, &tmpPgNum);
	}
	while (this->GetNext(*recFromFile)) {
		AddToPage(recFromFile, tmpPage, tmpFile, &tmpPgNum);
	}

	tmpPgNum++;
	tmpFile->AddPage(tmpPage, tmpPgNum);

	myFile->Close();
	tmpFile->Close();
	matchingPage = false;

	delete tmpFile;
	delete tmpPage;
	remove(filePath);
	rename("bin.tmp", filePath);
	myFile->Open(1, filePath);

}

void Sorted::AddToPage(Record* rec, Page* page, File* file, off_t* pgNum) {

	if (!page->Append(rec)) {
		*pgNum = *pgNum + 1;
		file->AddPage(page, *pgNum);
		page->EmptyItOut();
		page->Append(rec);

	}

}

void Sorted::Add(Record &addme) {
	setMode(writing);
	inPipe->Insert(&addme);

}

void Sorted::Load(Schema &myschema, char *loadpath) {
	if (loadpath == NULL) {
		cerr << "BAD! Specified load path is incorrect\n";
		exit(1);
	}
	queryOrderMakerSet = false;
	setMode(writing);
	Record record;
	FILE *tableFile = fopen(loadpath, "r");
	if (tableFile == NULL) {
		cerr << "BAD! File could not be opened: " << loadpath << "\n";
		exit(1);
	}

	while (record.SuckNextRecord(&myschema, tableFile)) {
		this->Add(record);
	}
	fclose(tableFile);
}

void Sorted::MoveFirst() {
	setMode(reading);
	currentPage = 0;
	queryOrderMakerSet = false;
	matchingPage = false;

	if ((myFile->GetLength() - 2) >= 0) {

		myFile->GetPage(myPage, currentPage);
		myPage->GetFirst(currentRecord, false);
	}
}
int Sorted::GetNext(Record &fetchme) {
	queryOrderMakerSet = false;
	setMode(reading);
	if (!myPage->GetNextRecord(fetchme)) {

		if ((currentPage) < (myFile->GetLength() - 2)) {
			currentPage++;
			myFile->GetPage(myPage, currentPage);
			myPage->GetFirst(currentRecord, false);
			myPage->GetNextRecord(fetchme);
		} else {
			return 0;
		}

	}
	pageAdded = true;
	return 1;
}

int Sorted::Close() {
	queryOrderMakerSet = false;
	setMode(reading);
	myFile->Close();
	return 1;
}

int Sorted::GetNext(Record &fetchme, CNF &cnf, Record &literal) {

	setMode(reading);
	if (pageAdded == false) {
		myFile->GetPage(myPage, 0);
		myPage->GetFirst(currentRecord, false);
		pageAdded = true;

	}

	//here have to make orderQuery using CNF and ordermaker

	if (queryOrderMakerSet == false) {

		int set = cnf.BuildQueryOrder(queryOrderMaker, myMetadata->mydata.om,
				literalOrderMaker);
		queryOrderMakerSet = true; //to check whether this order deleted

	}
	//fetch normally since no matching attributes are found in both
	if (!set) {

		while (GetNext(fetchme)) {
			if (ceng.Compare(&fetchme, &literal, &cnf))
				return 1;
		}

		return 0;
	}

	else {
		//check for the page which contatins this particular record
		if (matchingPage == false) {
			matchingPage = true;
			///get page using binary search, will load current page with this foundpage

			GetMatchPage(literal);
			bool foundMatchingRec = false;

			while (myPage->GetNextRecord(fetchme)) {

				if (ceng.Compare(&literal, &literalOrderMaker, &fetchme,
						&queryOrderMaker) == 0) {
					if (ceng.Compare(&fetchme, &literal, &cnf))
						return 1;

					foundMatchingRec = true;
					break;
				}
			}
			if (!foundMatchingRec)
				return 0;
		}

		while (true) {
			if (GetNext(fetchme)) {

				if (ceng.Compare(&literal, &literalOrderMaker, &fetchme,
						&queryOrderMaker) == 0) {
					if (ceng.Compare(&fetchme, &literal, &cnf))
						return 1;

				} else

					return 0;
			}

			else
				return 0;
		}

	}

	return 0;
}
//go to the page where the record matches the given cnf using binary search
//need to save the current page since search will move page number
// if a match occurs, then
// find the first record after current record which matches both cnf and sortorder,for this use binary sear

int Sorted::GetMatchPage(Record &literal) {

	   SavePage();


	int low = currentPage;
	int high = myFile->GetLength() - 2;

	int foundPage = BinarySearch(low, high, literal);

	if (foundPage == -1) // nothing found
			{

		  RestorePage();
		return foundPage;
	} else {
		if (foundPage >= 0) {

// fetch that page
			//cout << "found page in getmatch" << foundPage;
			currentPage = foundPage;
			myFile->GetPage(myPage, currentPage);
			myPage->GetFirst(currentRecord, false);

			 if (foundPage == (oldPageNum))
			 {

			  RestorePage();
			 return foundPage;
			 }


			Record rec;

			int pageNum = foundPage;
			while (pageNum >= 0) {

				if (GetNext(rec)
						&& ceng.Compare(&literal, &literalOrderMaker, &rec,
								&queryOrderMaker) > 0)
					break;
				//cout << "pageNum" << pageNum;
				// if 1st rec matches, goto prev page a
				pageNum--;
				  if (pageNum == (oldPageNum))
				 RestorePage();
				    else
				myFile->GetPage(myPage, pageNum);
			}

			foundPage = pageNum;

		}

		return foundPage;
	}
}

void Sorted::SavePage() {

	oldPage = myPage;
	oldPageNum = currentPage;

}

void Sorted::RestorePage() {

	//cout << "came here" << endl;

	currentPage = oldPageNum;
	myPage = oldPage;

}

int Sorted::BinarySearch(int low, int high, Record &literal) {

	if (low == high)
		return low;

	Record rec;

	int mid = (low + high) / 2;
	//cout << "mid page value" << mid << endl;
	myFile->GetPage(myPage, mid); // fetch mid-page in memory

	if (GetNext(rec)) { //query order maker and myOrder will be similar

		if (ceng.Compare(&literal, &literalOrderMaker, &rec, &queryOrderMaker)
				== 0)
			return mid;

		else if (ceng.Compare(&literal, &literalOrderMaker, &rec,
				&queryOrderMaker) < 0) {
			if (low == mid)
				return mid;
			else
				return BinarySearch(low, mid - 1, literal);
		} else

			return BinarySearch(mid + 1, high, literal);
	}
	return -1;
}

//--------------------------DBFile---------------------------------
DBFile::DBFile() {
}

DBFile::~DBFile() {
	if (genDB != NULL) {
		genDB->Close();
		delete genDB;
	}
}

int DBFile::Create(char *fpath, fType file_type, void *startup) {
	//Check if we have a valid path
	if (fpath == NULL) {
		return 0;
	}

	//Create the file based on ftype
	if (file_type == heap) {
		myMetadata.mydata.myType = heap;
		if (myMetadata.WriteMetaData(fpath) < 0) {
			cerr << "BAD!  Metadata file could not be written into\n";
			exit(1);
		}
		genDB = new Heap(fpath, 0, &myMetadata);
	} else if (file_type == sorted) {
		myMetadata.mydata.myType = sorted;
		myMetadata.mydata.om = *(((SortInfo*) startup)->myOrder);
		myMetadata.mydata.runLength = ((SortInfo*) startup)->runLength;
		if (myMetadata.WriteMetaData(fpath) < 0) {
			cerr << "BAD!  Metadata file could not be written into\n";
			exit(1);
		}
		genDB = new Sorted(fpath, 0, &myMetadata);
	} else {
		cerr << "Unknown type passed in DBFile\n";
		return 0;
	}
	return 1;
}

int DBFile::Open(char *fpath) {
	//Check if file path is valid
	if (fpath == NULL) {
		return 0;
	}

	//Load the metadata
	if (myMetadata.LoadMetaData(fpath) < 0) {
		cerr << "BAD!  Metadata file could not be loaded\n";
		exit(1);
	}

	//Create the file based on ftype
	if (myMetadata.mydata.myType == heap) {
		genDB = new Heap(fpath, 1, &myMetadata);
	} else if (myMetadata.mydata.myType == sorted) {
		genDB = new Sorted(fpath, 1, &myMetadata);
	} else {
		cerr << "Unknown type passed in DBFile\n";
		return 0;
	}

	return 1;
}

int DBFile::Close() {
	if (genDB != NULL) {
		genDB->Close();
		delete genDB;
		genDB = NULL;
	}
	return 1;
}

void DBFile::Load(Schema &myschema, char *loadpath) {
	if (genDB != NULL) {
		genDB->Load(myschema, loadpath);
	}
}

void DBFile::MoveFirst() {
	if (genDB != NULL) {
		genDB->MoveFirst();
	}
}

void DBFile::Add(Record &addme) {
	if (genDB != NULL) {
		genDB->Add(addme);
	}
}

int DBFile::GetNext(Record &fetchme) {
	if (genDB != NULL) {
		return genDB->GetNext(fetchme);
	}
}

int DBFile::GetNext(Record &fetchme, CNF &cnf, Record &literal) {
	if (genDB != NULL) {
		return genDB->GetNext(fetchme, cnf, literal);
	}
}
